package com.tian.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.tian.client.ChargeUserClient;
import com.tian.common.CommonResult;
import com.tian.common.PageResult;
import com.tian.common.UserInfoCache;
import com.tian.dto.ChargeUserDto;
import com.tian.dto.ChargeUserLoginResDto;
import com.tian.dto.UpdatePointReqDto;
import com.tian.dto.coupon.CouponRespDto;
import com.tian.dto.user.*;
import com.tian.entity.UserCoupon;
import com.tian.enums.*;
import com.tian.exception.BusinessException;
import com.tian.mapper.UserCouponMapper;
import com.tian.service.CouponService;
import com.tian.service.UserCouponService;
import com.tian.util.RedisConstantPre;
import com.tian.util.UserCacheUtil;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * {@code @description:} 我的优惠券
 *
 * @author tianwc 公众号：Java后端技术全栈
 * 在线刷题 1200+java面试题和1000+篇技术文章：<a href="https://woaijava.cc/">博客地址</a>
 * {@code @date:} 2024/1/23 9:32
 * {@code @version:} 1.0
 */
@Slf4j
@Service
public class UserCouponServiceImpl implements UserCouponService {
    @Resource
    private UserCouponMapper userCouponMapper;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private CouponService couponService;
    @Resource
    private ChargeUserClient chargeUserClient;

    @GlobalTransactional
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<Boolean> add(UserCouponAddReqDto userCouponAddReqDto) {
        ChargeUserLoginResDto userInfoCache = UserCacheUtil.getUser();
        //领取优惠券，重点是 防止用户多领取了
        //所以 我们给这个用户上锁
        RLock lock = redissonClient.getLock(RedisConstantPre.USER_INFO_ID_LOCK_PRE + userInfoCache.getId());
        lock.lock();
        try {
            Integer couponId = userCouponAddReqDto.getCouponId();
            //获取优惠券信息
            CommonResult<CouponRespDto> couponResult = couponService.findById(couponId);
            if (couponResult.getCode() != ResultCode.SUCCESS.getCode()) {
                return CommonResult.failed(couponResult.getCode(), couponResult.getMessage());
            }
            CouponRespDto couponRespDto = couponResult.getData();
            //优惠券已下架，不可领取
            if (couponRespDto.getStatus() != CouponStatusEnum.ACTIVE.getStatus()) {
                return CommonResult.failed(ResultCode.COUPON_DOWN);
            }
            /*
             * 判断优惠券是否已过期
             */
            Integer period = couponRespDto.getPeriod();
            Integer periodUnit = couponRespDto.getPeriodUnit();

            Date periodDate = CouponPeriodUnitEnum.caculatePeriodDate(new Date(), period, periodUnit);
            /*
             * times大于0 表示领取次数限制
             */
            if (couponRespDto.getTimes() > 0) {
                //统计该优惠券这个用户已领取次数
                int count = userCouponMapper.countTimes(userInfoCache.getId(), couponId);
                //判断是否已超
                if (couponRespDto.getTimes() <= count) {
                    return CommonResult.failed(ResultCode.USER_COUPON_LIMITER);
                }
            }

            UserCoupon userCoupon = new UserCoupon();
            userCoupon.setCouponId(couponId);
            userCoupon.setUserId(userInfoCache.getId());
            userCoupon.setPeriod(periodDate);
            userCoupon.setCreateTime(new Date());
            userCoupon.setStatus(UserCouponSatusEnum.INIT.getStatus());
            userCouponMapper.insert(userCoupon);

            UpdatePointReqDto updatePointReqDto = new UpdatePointReqDto();
            updatePointReqDto.setPoint(couponRespDto.getPoint());
            updatePointReqDto.setType(UserUpdatePointEnum.DEDUCT.getType());
            updatePointReqDto.setUserId(userInfoCache.getId());
            CommonResult<Boolean> booleanCommonResult = chargeUserClient.updatePoint(updatePointReqDto);
            if (booleanCommonResult.getCode() != ResultCode.SUCCESS.getCode()) {
                log.error("扣减用户积分失败");
                throw new BusinessException(booleanCommonResult.getMessage(), booleanCommonResult.getCode());
            }
        } finally {
            lock.unlock();
        }
        return CommonResult.success(Boolean.TRUE);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResult<Boolean> use(UserCouponUpdateReqDto userCouponUpdateReqDto) {

        List<UserCoupon> userCouponList = userCouponMapper.selectByIdList(userCouponUpdateReqDto.getIdList());
        List<Integer> couponIdList = userCouponList.stream()
                .map(UserCoupon::getCouponId)
                .collect(Collectors.toList());
        CommonResult<List<CouponRespDto>> couponResult = couponService.findByIdList(couponIdList);

        for (CouponRespDto couponRespDto : couponResult.getData()) {
            if (couponRespDto.getOverlay() == CouponOverlayEnum.UN_OVERLAY.getOverlay()) {
                return CommonResult.failed(ResultCode.COUPON_OVERLAY);
            }
        }
        for (UserCoupon userCoupon : userCouponList) {
            if (userCoupon.getStatus() == UserCouponSatusEnum.USED.getStatus()
                    || userCoupon.getStatus() == UserCouponSatusEnum.PRE_USE.getStatus()) {
                return CommonResult.failed(ResultCode.COUPON_USED);
            }
        }
        for (UserCoupon userCoupon : userCouponList) {
            userCoupon.setId(userCoupon.getId());
            userCoupon.setUseTime(new Date());
            userCoupon.setStatus(userCouponUpdateReqDto.getStatus());
            userCouponMapper.use(userCoupon);
        }
        return CommonResult.success(Boolean.TRUE);
    }

    @Override
    public CommonResult<PageResult<List<UserCouponPageRespDto>>> page(UserCouponPageReqDto userCouponPageReqDto) {
        Page<Object> page = PageHelper.startPage(userCouponPageReqDto.getCurrentPage(), userCouponPageReqDto.getPageSize());
        List<UserCoupon> userCouponList = userCouponMapper.page(userCouponPageReqDto);
        List<UserCouponPageRespDto> couponRespDtoList = new ArrayList<>();
        for (UserCoupon userCoupon : userCouponList) {
            UserCouponPageRespDto couponRespDto = new UserCouponPageRespDto();
            BeanUtils.copyProperties(userCoupon, couponRespDto);
            couponRespDto.setStatusStr(UserCouponSatusEnum.getDesByStatus(userCoupon.getStatus()));
            couponRespDtoList.add(couponRespDto);
        }
        PageResult<List<UserCouponPageRespDto>> listPageResult = PageResult.setData((int) page.getTotal(), page.getPageSize(), page.getPages(), page.getPageNum(), couponRespDtoList);
        return CommonResult.success(listPageResult);
    }

    @Override
    public CommonResult<UserCouponRespDto> findById(Long id) {
        RBucket<UserCoupon> bucket = redissonClient.getBucket(RedisConstantPre.COUPON_CONDITION_PRE + id);
        UserCoupon userCoupon = bucket.get();
        if (userCoupon == null) {
            userCoupon = userCouponMapper.selectByPrimaryKey(id);
            if (userCoupon == null) {
                //防止缓存穿透，存放一个空对象
                bucket.set(new UserCoupon());
                log.error("优惠券id={} 不存在", id);
                return CommonResult.failed(ResultCode.PARAMETER_ERROR);
            }
        }
        //防止缓存穿透
        if (userCoupon.getId() == null) {
            log.error("缓存穿透 优惠券id={} 不存在", id);
            return CommonResult.failed(ResultCode.PARAMETER_ERROR);
        }
        ChargeUserLoginResDto user = UserCacheUtil.getUser();

        if (!Objects.equals(userCoupon.getUserId(), user.getId())) {
            log.error("操作有误，id={} 不存在", id);
            return CommonResult.failed(ResultCode.PARAMETER_ERROR);
        }

        UserCouponRespDto userCouponRespDto = new UserCouponRespDto();
        BeanUtils.copyProperties(userCouponRespDto, userCoupon);
        return CommonResult.success(userCouponRespDto);
    }
}
